﻿using System;
using System.Linq.Expressions;
using System.Reflection;

namespace MoroMoro.Samples.ToDoList.Base.Domain.Expanders
{
    /// <summary>
    /// 式ツリー内で参照されているエンティティのプロパティを別の式に展開可能にします。
    /// </summary>
    /// <remarks>
    /// <para>
    /// 仕様や視点を使用して値を返すエンティティのプロパティがある場合、
    /// その仕様や視点では <see cref="IExpandable&lt;TEntity&gt;"/> インターフェイスを実装する必要があります。
    /// 仕様や視点を使用して値を返すエンティティのプロパティを式ツリー内で使用する仕様や視点では、
    /// そのプロパティに対する <see cref="IExpandable&lt;TEntity&gt;"/> 実装を元に生成した <see cref="Expander&lt;TEntity&gt;"/> を返すように、
    /// <see cref="Specifications.Specification&lt;TEntity&gt;"/> の <see cref="Specifications.Specification&lt;TEntity&gt;.GetExpander()"/> メソッド、
    /// または <see cref="Viewpoints.Viewpoint&lt;TEntity, TValue&gt;"/> の <see cref="Viewpoints.Viewpoint&lt;TEntity, TValue&gt;.GetExpander()"/> メソッドをオーバーライドします。
    /// これらを満たすことで、仕様や視点を使用して値を返すエンティティのプロパティを O/R マッパーのマッピング設定に登録しなくとも、
    /// 別の仕様や視点の式ツリー内で直接使用することが可能になります。
    /// </para>
    /// <para>
    /// 例えば、AProp というプロパティが AViewpoint という視点に処理を委譲している場合、
    /// AViewpoint には <see cref="IExpandable&lt;TEntity&gt;"/> インターフェイスを実装させます。
    /// そして、ASpec という仕様の式ツリー内で AProp を使用する場合、
    /// ASpec で <see cref="Specifications.Specification&lt;TEntity&gt;.GetExpander()"/> メソッドをオーバーライドし、
    /// AViewpoint を元に生成した <see cref="Expander&lt;TEntity&gt;"/> を返すよう実装します。
    /// これにより、ASpec の式ツリーは最終的に AProp を AViewpoint の式ツリーに置き換えた形となります。
    /// </para>
    /// </remarks>
    /// <example>
    /// <see cref="IExpandable&lt;TEntity&gt;"/> インターフェイスの実装方法を次のコード例に示します。
    /// <code>
    /// using System;
    /// using System.Linq.Expressions;
    /// using System.Reflection;
    /// using MoroMoro.Samples.ToDoList.Base.Domain.Expanders;
    /// using MoroMoro.Samples.ToDoList.Base.Domain.Viewpoints;
    /// 
    /// namespace MoroMoro.Samples.ToDoList.Domain.ToDos
    /// {
    ///     [Serializable]
    ///     public sealed class ToDoStateViewpoint : Viewpoint&lt;ToDo, ToDoState&gt;, IExpandable&lt;ToDo&gt;
    ///     {
    ///         protected override Expression&lt;Func&lt;ToDo, ToDoState&gt;&gt; CreateExpression()
    ///         {
    ///             return entity =>
    ///                 entity.CompletedDateTime.HasValue ? ToDoState.Completed :
    ///                 entity.TouchedDateTime.HasValue   ? ToDoState.Touched :
    ///                                                     ToDoState.Untouched;
    ///         }
    /// 
    /// 
    ///         PropertyInfo IExpandable&lt;ToDo&gt;.From()
    ///         {
    ///             return ReflectionHelper.ExtractPropertyInfo((ToDo entity) => entity.State);
    ///         }
    /// 
    ///         Expression IExpandable&lt;ToDo&gt;.To()
    ///         {
    ///             return Expression.Body;
    ///         }
    ///     }
    /// }
    /// </code>
    /// <see cref="Specifications.Specification&lt;TEntity&gt;.GetExpander()"/> メソッドの実装方法を次のコード例に示します。
    /// <code>
    /// using System;
    /// using System.Linq.Expressions;
    /// using MoroMoro.Samples.ToDoList.Base.Domain.Specifications;
    /// using MoroMoro.Samples.ToDoList.Base.Domain.Expanders;
    /// 
    /// namespace MoroMoro.Samples.ToDoList.Domain.ToDos
    /// {
    ///     [Serializable]
    ///     public sealed class ToDoStateSpecification : Specification&lt;ToDo&gt;
    ///     {
    ///         public ToDoStateSpecification(ToDoState value)
    ///         {
    ///             _value = value;
    ///         }
    /// 
    /// 
    ///         private readonly ToDoState _value;
    /// 
    /// 
    ///         protected override Expression&lt;Func&lt;ToDo, bool&gt;&gt; CreateExpression()
    ///         {
    ///             return entity => (entity.State == _value);
    ///         }
    /// 
    /// 
    ///         protected override Expander&lt;ToDo&gt; GetExpander()
    ///         {
    ///             return new Expander&lt;ToDo&gt;(new ToDoStateViewpoint());
    ///         }
    ///     }
    /// }
    /// </code>
    /// </example>
    /// <typeparam name="TEntity">エンティティの型。</typeparam>
    /// <seealso cref="Specifications.Specification&lt;TEntity&gt;"/>
    /// <seealso cref="Viewpoints.Viewpoint&lt;TEntity, TValue&gt;"/>
    public interface IExpandable<TEntity>
        where TEntity : class
    {
        /// <summary>
        /// 展開元のプロパティを取得します。
        /// </summary>
        /// <remarks>
        /// <para>
        /// このメソッドを実装する場合、
        /// <see cref="ReflectionHelper.ExtractPropertyInfo&lt;T, TResult&gt;(Expression&lt;Func&lt;T, TResult&gt;&gt;)"/> メソッドを
        /// 使用することで簡単に実装することができます。
        /// リフレクションを直接使用して <see cref="PropertyInfo"/> をこのメソッドを実装することもできますが、
        /// リフレクションを直接使用するとプロパティ名を文字列で示すことになるため、
        /// プロパティ名が間違っていてもコンパイルエラーは検出されません。
        /// <see cref="ReflectionHelper.ExtractPropertyInfo&lt;T, TResult&gt;(Expression&lt;Func&lt;T, TResult&gt;&gt;)"/> メソッドでは、
        /// プロパティの値を返すラムダ式を式ツリーとして受け取り、式ツリーからプロパティのメタデータを取りだします。
        /// 式ツリー内で使用されるメタデータはコンパイル時に直接コードに埋め込まれるため、
        /// プロパティ名の間違いがあればコンパイルエラーとして検出されます。
        /// </para>
        /// </remarks>
        /// <example>
        /// コード例については、<see cref="IExpandable&lt;TEntity&gt;"/> を参照してください。
        /// </example>
        /// <returns>展開元のプロパティ。</returns>
        PropertyInfo From();

        /// <summary>
        /// 展開先の式ツリーを取得します。
        /// </summary>
        /// <remarks>
        /// <para>
        /// このメソッドを仕様が実装する場合、<see cref="Specifications.Specification&lt;TEntity&gt;.Expression"/> プロパティから取得した
        /// 式ツリーの <see cref="LambdaExpression.Body"/> プロパティを返すように実装します。
        /// </para>
        /// <para>
        /// このメソッドを視点が実装する場合、<see cref="Viewpoints.Viewpoint&lt;TEntity, TValue&gt;.Expression"/> プロパティから取得した
        /// 式ツリーの <see cref="LambdaExpression.Body"/> プロパティを返すように実装します。
        /// </para>
        /// </remarks>
        /// <example>
        /// コード例については、<see cref="IExpandable&lt;TEntity&gt;"/> を参照してください。
        /// </example>
        /// <returns>展開先の式ツリー。</returns>
        Expression To();
    }
}
